package task09;

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicInteger;

public class ProducerConsumer {

    private final ExecutorService crawlerExecutor;
    private final ExecutorService indexerExecutor;

    private final List<BlockingDeque<File>> fileDequeList;
    private final AtomicInteger crawlerSequence;
    private final FileFilter fileFilter;

    private final AtomicInteger crawlerTasks;
    private final AtomicInteger indexerTasks;

    private volatile boolean shutdown;

    private static final int CRAWLER_COUNT = 2;
    private static final int INDEXER_COUNT = 10; // Runtime.getRuntime().availableProcessors();
    private static final int BOUND = 10;
    private static final int SERVICE_PERIOD = 100;

    public ProducerConsumer() {
        crawlerExecutor = Executors.newFixedThreadPool(CRAWLER_COUNT);
        indexerExecutor = Executors.newFixedThreadPool(INDEXER_COUNT);

        fileDequeList = new ArrayList<>();
        for (int i = 0; i < INDEXER_COUNT; i++)
            fileDequeList.add(new LinkedBlockingDeque<File>(BOUND));

        crawlerSequence = new AtomicInteger();
        fileFilter = new FileFilter() {
            @Override
            public boolean accept(File file) {
                return true;
            }
        };

        crawlerTasks = new AtomicInteger();
        indexerTasks = new AtomicInteger();
    }

    public void startIndexing(File[] roots) throws InterruptedException {
        for (File root : roots) {
            crawlerTasks.incrementAndGet();
            crawlerExecutor.submit(newCrawlerTask(root));
        }
        (new Thread(newServiceThread())).start();
    }

    public void shutdown() {
        shutdown = true;
    }

    private Runnable newCrawlerTask(final File root) {
        return new Runnable() {
            @Override
            public void run() {
                try {
                    File[] entries = root.listFiles(fileFilter);
                    if (entries != null) {
                        for (File entry : entries) {
                            if (entry.isDirectory()) {
                                crawlerTasks.incrementAndGet();
                                crawlerExecutor.submit(newCrawlerTask(entry));
                            } else if (!alreadyIndexed(entry)) {
                                BlockingDeque<File> fileDeque = fileDequeList.get(nextDeque());
                                fileDeque.put(entry);
                                indexerTasks.incrementAndGet();
                                indexerExecutor.submit(newIndexerTask(fileDeque));
                            }
                        }
                    }
                    crawlerTasks.decrementAndGet();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }

            private boolean alreadyIndexed(File file) {
                return false;
            }

            private int nextDeque() {
                return crawlerSequence.getAndIncrement() % fileDequeList.size();
            }
        };
    }

    private Runnable newIndexerTask(final BlockingDeque<File> fileDeque) {
        return new Runnable() {
            @Override
            public void run() {
                try {
                    if (!fileDeque.isEmpty()) {
                        indexFile(fileDeque.takeFirst());
                    } else {
                        for (BlockingDeque<File> deque : fileDequeList) {
                            if (!deque.isEmpty() && !deque.equals(fileDeque)) {
                                indexFile(deque.takeLast());
                                break;
                            }
                        }
                    }
                    indexerTasks.decrementAndGet();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }

            private void indexFile(File file) {
                System.out.println(Thread.currentThread().getName() + " " + file.getPath() + file.getName());
            }
        };
    }

    private Runnable newServiceThread() {
        return new Runnable() {
            @Override
            public void run() {
                try {
                    while (!shutdown) {
                        Thread.sleep(SERVICE_PERIOD);
                        if (shutdown) {
                            crawlerExecutor.shutdownNow();
                            indexerExecutor.shutdownNow();
                        }
                        if (crawlerTasks.intValue() == 0 && indexerTasks.intValue() == 0) {
                            crawlerExecutor.shutdown();
                            indexerExecutor.shutdown();
                            break;
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        };
    }
}